#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include "../../../clsv/error.h"
#include "fifoADT.h"
#include "../../../clsv/ipc/sig.h"

/*--------------------------------------------------------------------------*/

/* Initial connection identifier */
#define PATH_LEN (1024 + 1)
#define FIFO_CONN "Connect"
#define FIFO_DISC "Disconnect"
#define FIFO_WAIT_DISC	5

/*--------------------------------------------------------------------------*/

extern int counter;

/*--------------------------------------------------------------------------*/

struct fifoCDT
{
	int snd; /* File in which you write */
	int rcv; /* File in which you read */
	char sndFile[PATH_LEN];
	char rcvFile[PATH_LEN];
	char server;
} fifoCDT;

/*--------------------------------------------------------------------------*/

static int initFifoADT(char *read, char *write, fifoADT *fifo);
static int openFifo(char * name, int flags);
static int createFifo(char * name, int flags);
static void doNothing(int s);

/*--------------------------------------------------------------------------*/

extern int errno;
static int interrupted;

/*--------------------------------------------------------------------------*/

/* Common initialization of fifoADT */
static int
initFifoADT(char *read, char *write, fifoADT *fifo)
{
	fifoADT aux;

	*fifo = NULL;

	if ( (aux = malloc(sizeof(struct fifoCDT)) ) == NULL )
	{
		fprintf(stderr,"fifoADT: Initiating fifoADT, malloc error.\n");
		return ERROR_MALLOC; /* Not enough memory */
	}

	aux->server = 0;
	strcpy( aux->rcvFile, read);
	strcpy( aux->sndFile, write);

	*fifo = aux;

	return ERROR_OK;
}


/*--------------------------------------------------------------------------*/

/* Create a new fifo */
/* Returns the fifo fd or less than zero if error */
static int
createFifo(char * name, int flags)
{
	int error = ERROR_OK;

	if( (error = mkfifo(name, 0666)) == -1)
	{
		if( errno == EEXIST ) /* fifo already exists */
		{
			perror("fifoADT: Creating fifo, it already exists.\n");
			error = ERROR_EXIST;
		}
		else /* Not enough memory or system limit reached */
		{
			perror("fifoADT: Creating fifo, unexpected error.\n");
			error = ERROR_UNEX;
		}
	}
	else
	{
		error = openFifo(name, flags);
		if( error < 0)
		{
			if( remove(name) == -1)
			{
				perror("fifoADT: Creating, error removing file.\n");
				error = ERROR_UNEX;
			}

		}
	}

	return error;
}

/*--------------------------------------------------------------------------*/

/* Connect to an existing fifo */
/* Returns the fifo fd or less than zero if error */
static int
openFifo(char * name, int flags)
{
	int error = ERROR_OK;

	if( (error = open(name, flags)) == -1)
	{
		if( errno == EACCES ) /* No permission */
		{
			perror("fifoADT: Opening fifo, acces denied, check fifo permissions.\n");
			error = ERROR_ACCES;
		}
		else if( errno == ENOENT ) /* No message queue exists */
		{
			perror("fifoADT: Opening fifo, fifo does not exist.\n");
			error = ERROR_EXIST;
		}
		else /* Not enough memory or system limit reached */
		{
			perror("fifoADT: Opening fifo, unexpected error.\n");
			error = ERROR_UNEX;
		}
	}

	return error;
}

/*--------------------------------------------------------------------------*/

int
connFifo(char * read, char * write, fifoADT *fifo)
{
	char buf[2*PATH_LEN];
	int ans, auxRead, auxWrite;
	int error = ERROR_OK;

	/* Common initialization */
	ans = initFifoADT(read, write, fifo);
	if( ans != ERROR_OK )
		return ans;

	/* Open existing fifo */
	if( (ans = openFifo(read, O_RDWR)) < 0 )
	{
		error = ans;
	}
	else
	{	/* Save the read fifo */
		(*fifo)->rcv = ans;

		if( (ans = openFifo(write, O_WRONLY)) < 0 )
		{
			error = ans;
		}
		else
		{	/* Save the write fifo */
			(*fifo)->snd = ans;

			/* Now connect */
			/* Send initial message */
			strcpy(buf,FIFO_CONN);
			ans = sndFifo(*fifo,buf,PATH_LEN);
			if(  ans != PATH_LEN )
			{
				error = ans;
			}
			else
			{
				/* Receive new fifo */
				ans = rcvFifo(*fifo,buf,2*PATH_LEN);
				if( ans < 2*PATH_LEN )
				{
					if( ans < 0 )
					{
						error = ans;
					}
					else
					{
						fprintf(stderr,"fifoADT: Connecting, read less bytes than expected.\n");
						error = ERROR_UNEX;
					}
				}
				else
				{
/*					 if( remove() == -1 || remove(name) == -1 )
					 free(ADT)
					 Call initADT with new things*/

					/* Open the server's new Read queue */
					if( (auxRead = openFifo(buf, O_RDWR)) < 0 )
					{
						error = auxRead;
					}
					else
					{
						/* Open the server's new Write queue */
						if( (auxWrite = openFifo(buf+PATH_LEN, O_WRONLY)) < 0 )
						{
							error = auxWrite;
						}
						else
						{
							(*fifo)->rcv = auxRead;
							(*fifo)->snd = auxWrite;
						}
					}
				}
			}
		}
	}

	if( error != ERROR_OK )
	{
		free(*fifo);
		*fifo = NULL;
	}

	return error;
}

/*--------------------------------------------------------------------------*/

/* Create a new fifo to start listening */
int
bindFifo(char * read, char * write, fifoADT *fifo)
{
	int ans;
	int error = ERROR_OK;

	/* Common initialization */
	ans = initFifoADT(read, write, fifo);
	if( ans != ERROR_OK )
		return ans;

	(*fifo)->server = 1;

	/* Create fifo */
	if( (ans = createFifo(read, O_RDWR)) < 0 )
		error = ans;

	else
	{
		(*fifo)->rcv = ans;
		if( (ans = createFifo(write, O_RDWR)) < 0 )
		{
			error = ans;
			close((*fifo)->rcv);
			if( remove((*fifo)->rcvFile) == -1 )
			{
				perror("fifoADT: Closing, error removing file.\n");
			}
		}
		else
		{
			(*fifo)->snd = ans;
		}
	}

	if( error != ERROR_OK )
	{
		free(*fifo);
		*fifo = NULL;
	}

	return error;

}

/*--------------------------------------------------------------------------*/

int
acceptFifo(fifoADT fifo, fifoADT *new)
{
	char buf[2*PATH_LEN], aux[8];
	int error = ERROR_OK;

	*new = NULL;

	/* Receive a client */
	while( strcmp(buf,FIFO_CONN) )
	{
		if( rcvTFifo(fifo, buf, PATH_LEN,0) < PATH_LEN )
			return ERROR_UNEX;
	}

	sprintf(aux, "%d", counter);

	/* Create new file names */
	strcpy(buf , fifo->sndFile );
	strcpy(buf + PATH_LEN, fifo->rcvFile );
	strcat(buf, aux);
	strcat(buf + PATH_LEN, aux);

	/* Bind and create new fifoADT */
	error = bindFifo(buf + PATH_LEN, buf, new);
	if( error != ERROR_OK )
		return error;

	/* Send new fifos to client */
	error = sndFifo(fifo, buf, 2*PATH_LEN);
	if(  error != 2*PATH_LEN )
		return ERROR_UNEX;

	return ERROR_OK;
}

/*--------------------------------------------------------------------------*/

/* Returns the number of bytes actually copied into buf, or less than zero */
int
rcvTFifo(fifoADT fifo, void *buf, int count, int timeout)
{
	int signal = 0;
	int ans;
	int error = ERROR_OK;

	/* Check parameters */
	/* Count */

	if( count < 0 )
		return ERROR_PARAMS;
	if( count == 0 )
		return ERROR_OK;
	/* buf */
	if( buf == NULL )
		return ERROR_PARAMS;

	/* Receive message, waiting if necessary */
	/* Check if signal caugth. errno = EINTR */
	do
	{
		signal = 0;

		interrupted=1;
		if(timeout>0)
			setAlarm(timeout, doNothing);

		ans = read(fifo->rcv, buf, count);

		if(timeout>0)
			unsetAlarm();

		if( ans == -1 )
		{
			if( errno == EACCES )
			{
				perror("fifoADT: Receiving, acces denied, check queue permissions.\n");
				error = ERROR_ACCES;
			}
			else if(errno == EINTR && !interrupted)
			{
				signal = 1;
			}
			else
			{
				perror("fifoADT: Receiving, unexpected error.\n");
				error = ERROR_UNEX;
			}
		}
		else
		{
			error = ans;
		}
	}while( signal );

	return error;
}

/*--------------------------------------------------------------------------*/

/* Returns 0, less if error */
int
sndTFifo(fifoADT fifo, void *buf, int count, int timeout)
{
	int signal = 0;
	int ans;
	int error = ERROR_OK;

	/* Check parameters */
	/* count */

	if( count < 0 )
		return ERROR_PARAMS;
	if( count == 0 )
		return ERROR_OK;
	/* buf */
	if( buf == NULL )
		return ERROR_PARAMS;

	/* Send message, waiting if necessary */
	/* Check if signal caugth. errno = EINTR */
	do
	{
		signal = 0;
		interrupted=0;

		if(timeout>0)
			setAlarm(timeout, doNothing);

		ans = write(fifo->snd, buf, count);

		if(timeout>0)
			unsetAlarm();

		if(  ans == -1)
		{
			if( errno == EACCES )
			{
				perror("fifoADT: Sending, acces denied, check fifo permissions.\n");
				error = ERROR_ACCES;
			}
			else if( errno == EINTR && !interrupted)
			{
				signal = 1;
			}
			else
			{
				perror("fifoADT: Sending, unexpected error.\n");
				error = 0;
			}
		}
		else
		{
			error = ans;
		}
	}while( signal );

	return error;
}

/*--------------------------------------------------------------------------*/
/* No timeout read wrapper */
int
rcvFifo(fifoADT fifo, void *buf, int count)
{
	return rcvTFifo(fifo, buf, count, FIFO_WAIT_DISC);
}

/*--------------------------------------------------------------------------*/
/* No timeout send wrapper */
int sndFifo(fifoADT fifo, void *buf, int count)
{
	return sndTFifo(fifo, buf, count, FIFO_WAIT_DISC);
}

/*--------------------------------------------------------------------------*/

int
closeFifo(fifoADT fifo)
{
	int ans;
	char * buf;
	int error = ERROR_OK;

	buf = malloc(sizeof(char)*strlen(FIFO_DISC));

	if( fifo != NULL )
	{
		/* You are the creator of the queue */
		/* Wait for a final message */
		if( fifo->server )
		{
			/* Receive client's disconnect message. With a timeout */
			rcvFifo(fifo, buf, strlen(FIFO_DISC));
		}
		else
		{
			ans = sndTFifo(fifo, buf, strlen(FIFO_DISC),1);
		}

		/* close fifos */
		ans = close(fifo->rcv);
		if(  ans == -1)
		{
			if( errno == EACCES )
			{
				perror("fifoADT: Closing, acces denied, check queue permissions.\n");
				error = ERROR_ACCES;
			}
			else
			{
				perror("fifoADT: Closing, unexpected error.\n");
				error = ERROR_UNEX;
			}
		}

		ans = close(fifo->snd);
		if(  ans == -1)
		{
			if( errno == EACCES )
			{
				perror("fifoADT: Closing, acces denied, check fifo permissions.\n");
				error = ERROR_ACCES;
			}
			else
			{
				perror("fifoADT: Closing, unexpected error.\n");
				error = ERROR_UNEX;
			}
		}

		if( fifo->server )
		{
			if( remove(fifo->sndFile) == -1 )
			{
				perror("fifoADT: Closing, error removing file.\n");
			}

			if( remove(fifo->rcvFile) == -1 )
			{
				perror("fifoADT: Closing, error removing file.\n");
			}

			strcat(fifo->sndFile,"2");
			strcat(fifo->rcvFile,"2");

			if( remove(fifo->sndFile) == -1 && errno != ENOENT)
			{
				perror("fifoADT: Closing, error removing file.\n");
			}

			if( remove(fifo->rcvFile) == -1 && errno != ENOENT )
			{
				perror("fifoADT: Closing, error removing file.\n");
			}

		}

		free(fifo);
	}

	return error;

}

/*--------------------------------------------------------------------------*/

static void
doNothing(int s)
{
	interrupted = 1;
}
